#include <fuse.h>
#include <ftdi.h>
#include <stdio.h>    // printf
#include <stdlib.h>   // system
#include <string.h>   // memset
#include <errno.h>    // error numbers
#include <unistd.h>   // mk/rmdir

#define numel(x) (sizeof(x)/sizeof(x[0]))

#define OUTPUT_PIN 0xFF

static int device_is_ready;
static struct ftdi_context ftdic;

static int ftdi_getattr(const char *path, struct stat *stbuf)
{
	int res = 0;

	memset(stbuf, 0, sizeof(struct stat));
	if(strcmp(path, "/") == 0){
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 2;
	}else if(strcmp(path, "/chb") == 0){
		stbuf->st_mode = S_IFREG | 0666;
		stbuf->st_nlink = 1;
		stbuf->st_size = 0;
	}else{
		res = -ENOENT;
	}

	return res;
}

static int ftdi_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                        off_t offset, struct fuse_file_info *fi)
{
	(void) offset;
	(void) fi;

	if(strcmp(path, "/") != 0)
		return -ENOENT;

	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	filler(buf, "chb", NULL, 0);

	return 0;
}

static int ftdi_open(const char *path, struct fuse_file_info *fi)
{
	if(strcmp(path, "/chb") != 0)
		return -ENOENT;
	
	if(device_is_ready) return 0;
	
	if (ftdi_init(&ftdic) < 0){
		//fprintf(stderr, "ftdi_init failed\n");
		return -ENODEV;
	}
	
	ftdi_set_interface(&ftdic, INTERFACE_B);
	
	if(ftdi_usb_open(&ftdic, 0x0403, 0x6010) < 0){
		//fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(&ftdic));
		device_is_ready = 0;
		return -ENODEV;
	}
	
	ftdi_set_bitmode(&ftdic, OUTPUT_PIN, BITMODE_BITBANG);
	
	device_is_ready = 1;
	
	return 0;
}

static int ftdi_read(const char *path, char *buf, size_t size, off_t offset,
                      struct fuse_file_info *fi)
{
	// there's nothing to read, it's just an output port!
	return 0;
}

int ftdi_write(const char *path, const char *buf, size_t size, off_t offset,
                struct fuse_file_info *fi)
{
	if( ftdi_write_data(&ftdic, (unsigned char *)buf, size) <= 0){
		// on error, tell that no bytes could be written
		return 0;
	}
	
	return size;
}

int ftdi_release(const char *path, struct fuse_file_info *fi)
{
	return 0;
}

int ftdi_truncate(const char *path, off_t size){
	return 0;
}

static struct fuse_operations ftdi_oper = {
	.getattr    = ftdi_getattr,
	.readdir    = ftdi_readdir,
	.open       = ftdi_open,
	.read       = ftdi_read,
	.write      = ftdi_write,
	.release    = ftdi_release,
	.truncate   = ftdi_truncate,
};

int main(int argc, char *argv[])
{
	char *argv_[] = {"ftdi", "/dev/ftdi", "-o", "allow_other", NULL};
	int retval;
	
	mkdir("/dev/ftdi", 755);
	
	if( argc == 1 ){
		argc = numel(argv_)-1;
		argv = argv_;
	}
	
	retval = fuse_main(argc, argv, &ftdi_oper);
	
	rmdir("/dev/ftdi");
	
	return retval;
}

